#include"Socket.hpp"
#include"log.hpp"

#define NUM sizeof(fd_set)*8//最大文件描述符个数
const int deafult_num = -1;
class SelectServer{
    public:
        SelectServer(uint16_t port)
            :_port(port),_listen_socket(new TcpSocket(_port))//
        {}
        void Init()
        {
            _listen_socket->Build_Socket(_port);
            for(int i = 0;i<NUM;i++){
                _fd_array[i] = deafult_num;
            }
            //将listenfd加入到数组中
            _fd_array[0] = _listen_socket->Fd();
        }
        void Loop()
        {
            fd_set read_fds;//读文件描述符集合
            _is_running = true;
            while(_is_running){
                //accept是阻塞的，listensockfd获取新连接，本质是一种IO
                //把listensockfd注册到select中，监听新连接(帮忙‘等’)
                FD_ZERO(&read_fds);//清空集合
                int maxfd = -1;
                for(int i = 0;i<NUM;i++){
                    if(_fd_array[i]!= deafult_num){//合法的fd
                        FD_SET(_fd_array[i], &read_fds);//把合法文件描述符加入集合
                        if(_fd_array[i] >maxfd){//更新最大文件描述符
                            maxfd = _fd_array[i];
                        }
                    }else{
                        continue;
                    }
                }
                struct timeval timeout = {5,0};//设置超时时间为5s

                // int n = select(_listen_socket->Fd()+1, &read_fds, nullptr ,nullptr, nullptr);//select阻塞等待，直到有事件发生
                int n = select(maxfd+1, &read_fds, nullptr ,nullptr, &timeout);//select轮询，超时时间为5s
                switch(n)
                {
                    case 0:
                        std::cout<<"time out"<<timeout.tv_sec<<" : "<<timeout.tv_usec<<std::endl;
                        break;
                    case -1:
                        perror("select error");
                        break;
                    default://内核告诉用户有fd就绪(可能是监听sockfd获取新连接，也可能是其他合法fd数据准备好了),处理事件
                        std::cout<<"new connection "<<timeout.tv_sec<<" : "<<timeout.tv_usec<<std::endl;
                        Dispatch(read_fds);//根据fd_set分配到相应的处理函数
                        break;
                }
            }
            _is_running = false;
        }
        void Accept()//listensockfd获取新连接成功，不需要等（select帮等了），直接调用accept,把获取到的新链接加入到辅助数组中，从而添加到fd_set中
        {
            struct sockaddr_in client_addr;
            int new_sockfd = _listen_socket->Accept((struct sockaddr*)&client_addr);//不会阻塞
            if(new_sockfd < 0){
                return;
            }else{//新连接
                std::string client_ip = inet_ntoa(client_addr.sin_addr);//获取客户端ip
                uint16_t client_port = ntohs(client_addr.sin_port);//获取客户端端口
                LOG(log_level::INFO)<<"new connection from "<<client_ip<<":"<<client_port;
            }
            //将新连接的sockfd加入到数组中
            int pos = 0;
            for(pos = 0;pos<NUM;pos++){//只是连接成功了，数据是否准备成功不清楚,即将new_sockfd加入select中，等待数据准备
                if(_fd_array[pos] == deafult_num){//找到一个空位
                    _fd_array[pos] = new_sockfd;
                    break;
                }
            }
            if(pos == NUM){//数组已满
                LOG(log_level::ERROR)<<"服务器已满";
                close(new_sockfd);
                return;
            }
        }
        void DealData(int sockfd,int pos)//处理IO数据
        {
            //只是写一个demo,数据的处理想要完备，必须引入协议，太麻烦了
            char buffer[1024];
            ssize_t n = recv(sockfd, buffer, sizeof(buffer)-1, 0);//不会阻塞
            if(n>0){
                buffer[n] = '\0';
                std::cout<<"client say: "<<buffer<<std::endl;
                send(sockfd, buffer, n, 0);//发是可能会阻塞的，因为只处理了读的逻辑，用poll,epoll写这份代码时会处理
                
            }else if(n==0){
                LOG(log_level::INFO)<<"client quit "<<_fd_array[pos];
                close(sockfd);
                _fd_array[pos] = deafult_num;
            }else{
                LOG(log_level::ERROR)<<"recv error "<<_fd_array[pos];
                close(sockfd);
                _fd_array[pos] = deafult_num;
            }
        }
        void Dispatch(fd_set &read_fds)
        {
            for(int i = 0;i<NUM;i++){
                if(_fd_array[i]==deafult_num)//不是关注的文件描述符
                    continue;
                else if(_fd_array[i]==_listen_socket->Fd() && FD_ISSET(_listen_socket->Fd(), &read_fds)){//是监听sockfd，且fd_set里准备就绪了
                    Accept();
                }else if(FD_ISSET(_fd_array[i], &read_fds)){//其他合法文件描述符准备好数据了，进行数据IO处理
                    DealData(_fd_array[i],i);
                }
            }
        }
        ~SelectServer()
        {}
    private:
        uint16_t _port;
        std::unique_ptr<Socket> _listen_socket;
        bool _is_running = false;
        int _fd_array[NUM];//文件描述符数组
};